﻿// Copyright (C) 2012 Jeff Tanner <jeff00seattle@gmail.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

using System;
using System.IO;
using System.Diagnostics;
using System.Configuration;
using System.Configuration.Install;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Collections.Generic;

using WindowsAzure_REST;

namespace WindowsAzure_FileBackup_Common
{
    partial class Common
    {
        /// <summary>
        /// Allowed file system types
        /// </summary>
        public enum EnumFileSystemType
        {
            /// <summary>
            /// Type file
            /// </summary>
            File,
            /// <summary>
            /// Type directory
            /// </summary>
            Directory,
            /// <summary>
            /// Unknown file system type
            /// </summary>
            Unknown
        }

        /// <summary>
        /// Gets equivalent blob path for provided file path.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        protected static string GetBlobPath(string filePath)
        {
            string filePathPartial = filePath.Substring(Common.WindowsAzureFileBackupRootDir.Length);

            string pattern = @"\\";
            string replacement = "/";
            char[] dirTrim = { '\\', '.' };
            return Regex.Replace(filePathPartial.Trim(dirTrim), pattern, replacement);
        }

        /// <summary>
        /// Gets the type of the file.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <returns></returns>
        private EnumFileSystemType getFileSystemType(string filePath)
        {
            EnumFileSystemType fileType = EnumFileSystemType.Unknown;
            if (File.Exists(filePath))
            {
                fileType = EnumFileSystemType.File;
            }
            else if (Directory.Exists(filePath))
            {
                fileType = EnumFileSystemType.Directory;
            }
            else
            {
                string errorMsg =
                    String.Format(
                        "GetFileSystemType: Does not exist: {0}",
                        filePath
                        );

                this.EventLog.WriteEntry(
                    errorMsg,
                    EventLogEntryType.Error
                    );

                throw new ApplicationException(errorMsg);
            }

            return fileType;
        }

        /// <summary>
        /// Reads data from a stream until the end is reached. The
        /// data is returned as a byte array. An IOException is
        /// thrown if any of the underlying IO calls fail.
        /// </summary>
        /// <param name="stream">The stream to read data from</param>
        /// <param name="initialLength">The initial buffer length</param>
        private byte[] readFileFully(Stream stream, int initialLength)
        {
            byte[] binary = null;
            try
            {
                // If we've been passed an unhelpful initial length, just
                // use 32K.
                if (initialLength < 1)
                {
                    initialLength = 32768;
                }

                byte[] buffer = new byte[initialLength];
                int read = 0;

                int chunk;
                while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
                {
                    read += chunk;

                    // If we've reached the end of our buffer, check to see if there's
                    // any more information
                    if (read == buffer.Length)
                    {
                        int nextByte = stream.ReadByte();

                        // End of stream? If so, we're done
                        if (nextByte == -1)
                        {
                            return buffer;
                        }

                        // Nope. Resize the buffer, put in the byte we've just
                        // read, and continue
                        byte[] newBuffer = new byte[buffer.Length * 2];
                        Array.Copy(buffer, newBuffer, buffer.Length);
                        newBuffer[read] = (byte)nextByte;
                        buffer = newBuffer;
                        read++;
                    }
                }

                // Buffer is now too big. Shrink it.
                binary = new byte[read];
                Array.Copy(buffer, binary, read);
            }
            catch (Exception ex)
            {
                this.EventLog.WriteEntry(
                    String.Format("ReadFully | Exception: {0}", ex.Message),
                    EventLogEntryType.Error
                    );
            }

            return binary;
        }


        public int FileCount(System.IO.DirectoryInfo root)
        {
            System.IO.FileInfo[] files = null;
            System.IO.DirectoryInfo[] subDirs = null;

            try
            {
                string dirPath = root.FullName;

                files = root.GetFiles();
                subDirs = root.GetDirectories();

                return files.Length;
            }
            catch (System.UnauthorizedAccessException)
            {
            }
            catch (System.IO.DirectoryNotFoundException)
            {
            }
            catch (System.Exception)
            {
            }

            return 0;
        }

        public void BackupDirectoryCount(System.IO.DirectoryInfo root, ref int filesCount)
        {
            System.IO.FileInfo[] files = null;
            System.IO.DirectoryInfo[] subDirs = null;

            try
            {
                string dirPath = root.FullName;

                files = root.GetFiles();
                subDirs = root.GetDirectories();

                if (0 == files.Length && 0 == subDirs.Length)
                {
                    return;
                }

                filesCount += files.Length;

                if (subDirs.Length > 0)
                {
                    foreach (System.IO.DirectoryInfo dirInfo in subDirs)
                    {
                        // Resursive call for each subdirectory.
                        this.BackupDirectoryCount(dirInfo, ref filesCount);
                    }
                }
            }
            catch (System.UnauthorizedAccessException)
            {
            }
            catch (System.IO.DirectoryNotFoundException)
            {
            }
            catch (System.Exception)
            {
            }
        }

        /// <summary>
        /// Backups a directory.
        /// </summary>
        /// <param name="root">The root.</param>
        public void BackupDirectory(System.IO.DirectoryInfo root)
        {
            System.IO.FileInfo[] files = null;
            System.IO.DirectoryInfo[] subDirs = null;

            try
            {
                string dirPath = root.FullName;

                files = root.GetFiles();
                subDirs = root.GetDirectories();

                if (0 == files.Length && 0 == subDirs.Length)
                {
                    string blobFolderPath = Common.GetBlobPath(dirPath);
                    this.BlobCreate(dirPath, dirPath, blobFolderPath);

                    return;
                }

                if (files.Length > 0)
                {
                    foreach (System.IO.FileInfo fi in files)
                    {
                        string filePath = fi.FullName;
                        string blobPath = Common.GetBlobPath(filePath);

                        this.BlobCreate(dirPath, filePath, blobPath);
                    }
                }

                if (subDirs.Length > 0)
                {
                    foreach (System.IO.DirectoryInfo dirInfo in subDirs)
                    {
                        // Resursive call for each subdirectory.
                        this.BackupDirectory(dirInfo);
                    }
                }
            }
            catch (System.UnauthorizedAccessException)
            {
            }
            catch (System.IO.DirectoryNotFoundException)
            {
            }
            catch (System.Exception)
            {
            }
        }

        public void RecoveryDirectory(System.IO.DirectoryInfo root)
        {
            try
            {
                string dirPath = root.FullName;

                List<string> blobNames = Common.BlobREST.ListBlobs(Common.WindowsAzureStorageBlobContainerName);

                foreach (string blobName in blobNames)
                {
                    SortedList<string, string> blobMetadata = Common.BlobREST.GetBlobMetadata(Common.WindowsAzureStorageBlobContainerName, blobName);
                    string fileBackupPath = blobMetadata[String.Format("x-ms-meta-{0}", EnumFileMetadata.PathWithName)];

                    string relativePath = String.Empty;
                    Common.GetRelativePath(Common.WindowsAzureFileBackupRootDir, fileBackupPath, out relativePath);

                    string fileRecoveryPath = String.Format(@"{0}\{1}", Common.WindowsAzureFileRecoveryRootDir, relativePath);
                    if (File.Exists(fileRecoveryPath))
                    {
                        File.Delete(fileRecoveryPath);
                    }

                    DirectoryInfo dirInfo = new DirectoryInfo(fileRecoveryPath);
                    string dirRecoveryPath = fileRecoveryPath.Substring(0, fileRecoveryPath.Length - dirInfo.Name.Length);
                    Directory.CreateDirectory(dirRecoveryPath);
                    Common.BlobREST.GetBlobFile(Common.WindowsAzureStorageBlobContainerName, blobName, fileRecoveryPath);

                    if (File.Exists(fileRecoveryPath))
                    {
                        DateTime creationTimeUtc = DateTime.Parse(blobMetadata[String.Format("x-ms-meta-{0}", EnumFileMetadata.CreationTimeUtc)]);
                        DateTime lastAccessTimeUtc = DateTime.Parse(blobMetadata[String.Format("x-ms-meta-{0}", EnumFileMetadata.LastAccessTimeUtc)]);
                        DateTime lastWriteTimeUtc = DateTime.Parse(blobMetadata[String.Format("x-ms-meta-{0}", EnumFileMetadata.LastWriteTimeUtc)]);

                        File.SetCreationTimeUtc(fileRecoveryPath, creationTimeUtc);
                        File.SetLastAccessTimeUtc(fileRecoveryPath, lastAccessTimeUtc);
                        File.SetLastWriteTimeUtc(fileRecoveryPath, lastWriteTimeUtc);

                        FileAttributes fileAttributes = (FileAttributes)long.Parse(blobMetadata[String.Format("x-ms-meta-{0}", EnumFileMetadata.Attributes)]);
                        File.SetAttributes(fileRecoveryPath, fileAttributes);
                    }
                }

            }
            catch (System.UnauthorizedAccessException)
            {
            }
            catch (System.IO.DirectoryNotFoundException)
            {
            }
            catch (System.Exception ex)
            {
                this.EventLog.WriteEntry(
                    String.Format("RecoveryDirectory: Exception: {0}", ex.Message),
                    EventLogEntryType.Error
                  );
            }
        }

        public static void DeleteAllFiles(System.IO.DirectoryInfo root)
        {
            Array.ForEach(Directory.GetFiles(root.FullName),
                          delegate(string path) { File.Delete(path); });
        }
    }
}
